package main

import (
	"os"
	"fmt"
	"strings"
	"io/ioutil"
	"os/exec"
	"flag"
	"go/build"
	"path/filepath"
)

func ChkErr(err error) bool{
	if err != nil {
		fmt.Println("Error: ", err.Error())
		os.Exit(1)
	}
	return true
}

func igoStrSpaceSplit(str string) []string {
	var ret []string
	var bStart, bSeparator bool

	str = strings.TrimSpace(str)
	length := len(str)
	bStart = true
	for i, j:=0, 0; i<length; i++ {
		if (i == 0 && string(str[i]) == "'") {
			bSeparator = true
			j = 1
		}
		if !bStart {
			if string(str[i]) == " " {
				bStart = true
				j = i + 1
				if string(str[i + 1]) == "'" {
					bSeparator = true
					i ++
					j = i + 1
				}
			}
		} else {
			//bStart is true
			if bSeparator {
				if string(str[i]) == "'" {
					if (i + 1 < length && string(str[i + 1]) == " ") || (i + 1 >= length) {
						bSeparator = false
						bStart = false
						s := strings.TrimSpace(str[j:i])
						if len(s) > 0 {
							ret = append(ret, s)
						}
						j = i + 1
					}
				}
			} else {
				if string(str[i]) == " " {
					s := strings.TrimSpace(str[j:i])
					if len(s) > 0 {
						ret = append(ret, s)
					}
					j = i + 1
					if string(str[i + 1]) == "'" {
						bSeparator = true
						i ++
						j = i + 1
					}
				} else if i == length - 1 {
					s := strings.TrimSpace(str[j:i + 1])
					if len(s) > 0 {
						ret = append(ret, s)
					}
				}
			}
		}
	}
	return ret
}

func igoListPackages(path string) []string{
	var ret []string
	s := filepath.Join(path, "src")
	fis, err := ioutil.ReadDir(s)
	if err != nil {
//		fmt.Println("Testing: ", err.Error())
		return ret
	}
	for j:=0; j<len(fis); j++ {
		if fis[j].IsDir() {
			_, pkgerr := build.ScanDir(filepath.Join(s, fis[j].Name()))
			if pkgerr == nil {
				ret = append(ret, fis[j].Name())
			}
		}
	}
	return ret
}

const (
	CCONFIGFILE = "gopath.cfg"
)

type TGopath struct {
	curDir string
	cfgFile string
	gopath []string
	pkgs map[string][]string
}

func (me *TGopath) Create() {
	var err error
	me.curDir, err = os.Getwd()
	me.pkgs = make(map[string][]string)
	me.cfgFile, _ = filepath.Abs(CCONFIGFILE)
	ChkErr(err)
}

func (me *TGopath) Pwd() string {
	return me.curDir
}

func (me *TGopath) GetGopath() []string {
	return me.gopath
}
func (me *TGopath) AddPath(newPath string) {
	if len(newPath) == 0 {
		return
	}
	switch string(newPath[0]) {
	case "/":
		me.gopath = append(me.gopath, newPath)
	case "~":
		home := os.Getenv("HOME")
		me.gopath = append(me.gopath, filepath.Join(home, strings.TrimLeft(newPath, "~")))
	default:
		me.gopath = append(me.gopath, strings.TrimRight(me.curDir, "/") + "/" + newPath)
	}

}

func (me *TGopath) ReadGopathFromConfigFile() bool {
	byteFileContent, err := ioutil.ReadFile(me.cfgFile)
	if err != nil {
		return false
	}
	strFileContent := string(byteFileContent)
	gp := strings.Split(strFileContent, "\n")

	for i:=0; i<len(gp); i++ {
		s := strings.TrimRight(gp[i], "\r")
		if len(s) > 0 {
			me.AddPath(s)
		}
	}
	return true
}

func (me *TGopath) BuildPackageList() {
	for i:=0; i<len(me.gopath); i++ {
		me.pkgs[me.gopath[i]] = igoListPackages(me.gopath[i])
	}
}

func (me *TGopath) GetPackageListFromPath(path string) []string{
	return me.pkgs[path]
}

func (me *TGopath) AutoBuildPath() {
	fis, err := ioutil.ReadDir(me.curDir)
	ChkErr(err)
	for i:=0; i<len(fis); i++ {
		if fis[i].IsDir() {
			me.gopath = append(me.gopath, filepath.Join(me.curDir, fis[i].Name()))
		}
	}
}

type TIgo struct {
	gopath TGopath
	originGopath string
	bManualPath, bManualPkg bool
	mPkgs []string
//	cmd exec.Cmd
	Gotool string
	Args []string
}

func (me *TIgo) Create() {
	me.gopath.Create()
	me.Gotool = os.Getenv("GOBIN")
	if len(me.Gotool) == 0 {
		me.Gotool = os.Getenv("GOROOT")
		if len(me.Gotool) == 0 {
			os.Exit(2)
		}
		me.Gotool = strings.TrimRight(me.Gotool, "/") + "/bin/go"
	} else {
		me.Gotool = strings.TrimRight(me.Gotool, "/") + "/go"
	}
	me.originGopath = os.Getenv("GOPATH")
}

func (me *TIgo) HandleArgs() {
	var manualGoPath, manualPkgs string
	var workDir, cfgFile string
	flag.StringVar(&manualGoPath, "p", "", "assign gopath manually")
	flag.StringVar(&manualPkgs, "m", "", "point out which package or which path to be build")
	flag.StringVar(&workDir, "d", "", "set working dir")
	flag.StringVar(&cfgFile, "f", "", "set gopath config file")
	flag.Parse()
	if flag.NArg() == 0 {
		me.Args = append(me.Args, "install")
	} else {
		me.Args = append(me.Args, flag.Args()...)
	}

	if len(cfgFile) > 0 {
		abs, err := filepath.Abs(cfgFile)
		if err == nil {
			me.gopath.cfgFile = abs
			me.gopath.curDir = filepath.Dir(abs)
		}
	} else if len(workDir) >0 {
		abs, err := filepath.Abs(workDir)
		if err == nil {
			me.gopath.curDir = abs
		}
	}
	var slManualGoPath, slManualPkgs []string
	if len(manualGoPath) > 0 {
		me.bManualPath = true
		slManualGoPath = igoStrSpaceSplit(manualGoPath)
		for i:=0; i<len(slManualGoPath); i++ {
			me.gopath.AddPath(slManualGoPath[i])
		}
	} else if !me.gopath.ReadGopathFromConfigFile() {
		me.gopath.AutoBuildPath()
	}

	me.gopath.BuildPackageList()

	if len(manualPkgs) > 0 {
		me.bManualPkg = true
		slManualPkgs = igoStrSpaceSplit(manualPkgs)
		for i:=0; i<len(slManualPkgs); i++ {
			givenPath := strings.TrimRight(me.gopath.Pwd(), "/") + "/" + slManualPkgs[i]
			pkgs := me.gopath.GetPackageListFromPath(givenPath)
			me.mPkgs = append(me.mPkgs, pkgs...)
		}
		for _, v := range me.gopath.pkgs {
			for i:=0; i<len(v); i++ {
				for j:=0; j<len(slManualPkgs); j++ {
					if v[i] == slManualPkgs[j] {
						me.mPkgs = append(me.mPkgs, v[i])
					}
				}
			}
		}
	} else {
		me.mPkgs = []string{"all"}
	}

	me.Args = append(me.Args, me.mPkgs...)
	
	gpl := me.gopath.GetGopath()
	for _, s := range gpl {
		me.originGopath = me.originGopath + ":" + s
	}
	me.originGopath = strings.TrimLeft(me.originGopath, ":")
	os.Setenv("GOPATH", me.originGopath)
}

func main(){
	var igo TIgo
	igo.Create()
	igo.HandleArgs()

	fmt.Println("GOPATH is :\n\t", os.Getenv("GOPATH"))
	cmd := exec.Command(igo.Gotool, igo.Args[:]...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	ChkErr(err)
}
